package com.upstairstechnology.oi.determination;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Required;

/**
 * Base class for determination strategies that use a command-line
 * interface/process/console.
 * 
 * @author Jeff Jensen
 */
public abstract class AbstractDeterminationStrategyCommand extends
        AbstractDeterminationStrategy {
    /** Logger instance. */
    private static final Logger LOGGER =
            LoggerFactory.getLogger(AbstractDeterminationStrategy.class);

    /** Logback Marker. */
    public static final String PROCESSING = "Processing";

    /** Logback Marker. */
    public static final String BRANCHSPEC = "Branchspec";

    /** Logback Marker. */
    public static final String SOURCETARGET = "SourceTarget";

    /** Configuration properties for the determination strategy. */
    private DeterminationStrategyConfigurationCommand configuration;

    /** Strategy to process output from the command console/process. */
    private CommandReader commandReader;

    /**
     * {@inheritDoc}
     */
    @Override
    protected Set<OutstandingIntegration> templateProcessBranchspec(
            IntegrationCheck check) throws IOException {
        MDC.put(PROCESSING, BRANCHSPEC);

        DeterminationStrategyConfigurationCommand cfg = getConfiguration();

        String branchspec = check.getBranchspec();

        List<String> cmdList = new ArrayList<String>();
        cmdList.add(cfg.getProgram());
        cmdList.addAll(cfg.getProgramArgumentsBranchspecList());
        cmdList.add(branchspec);

        Set<OutstandingIntegration> set =
                processIntegrationCheck(check, cmdList);

        MDC.remove(PROCESSING);

        return set;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Set<OutstandingIntegration> templateProcessSourceTarget(
            IntegrationCheck check) throws IOException {
        MDC.put(PROCESSING, SOURCETARGET);

        DeterminationStrategyConfigurationCommand cfg = getConfiguration();
        String source = check.getSourceCodeline();
        String target = check.getTargetCodeline();

        List<String> cmdList = new ArrayList<String>();
        cmdList.add(cfg.getProgram());
        cmdList.addAll(cfg.getProgramArgumentsSourceTargetList());
        cmdList.add(source);
        cmdList.add(target);

        Set<OutstandingIntegration> set =
                processIntegrationCheck(check, cmdList);

        MDC.remove(PROCESSING);

        return set;
    }

    /**
     * Process an integration check.
     * 
     * @param check
     *            The integration check to process.
     * @param cmdList
     *            Command and its arguments to execute to determine outstanding
     *            integrations.
     * @return Set of determined outstanding integrations.
     * @throws IOException
     *             On processing errors.
     */
    protected Set<OutstandingIntegration> processIntegrationCheck(
            IntegrationCheck check, List<String> cmdList) throws IOException {

        LOGGER.debug("Executing command={}", cmdList);
        Set<OutstandingIntegration> set =
                OutstandingIntegrationFactory.makeEmptySet();

        ProcessBuilder processBuilder = new ProcessBuilder(cmdList);
        Process process = processBuilder.start();

        BufferedReader inStd = commandReader.setupIn(process.getInputStream());
        BufferedReader inErr = commandReader.setupIn(process.getErrorStream());

        // TODO different SCMs do this differently, so refactor to
        // - "DeterminationProcessorStrategyPerforceLatest" or
        // - push into subclass
        String lineStd = commandReader.readNextLine(cmdList, inStd, inErr);
        while (lineStd != null) {
            LOGGER.debug("lineStd={}", lineStd);

            OutstandingIntegration oi =
                    OutstandingIntegrationFactory.newInstance();
            oi.setIntegrationCheck(check);
            set.add(oi);

            Set<OutstandingIntegration.File> fileSet =
                    OutstandingIntegrationFactory.makeEmptyFileSet();
            oi.setFiles(fileSet);

            processChangeLine(lineStd, oi);

            lineStd = commandReader.readNextLine(cmdList, inStd, inErr);
            while (lineStd != null && lineStd.startsWith("...")) {
                LOGGER.debug("lineStd={}", lineStd);

                processFileLine(lineStd, oi);

                lineStd = inStd.readLine();
            }

            LOGGER.debug("Created oi={}", oi);
        }

        inStd.close();
        inErr.close();
        process.destroy();

        return set;
    }

    /**
     * Process a response line containing the change information.
     * 
     * @param line
     *            Response line with change information.
     * @param oi
     *            Outstanding integration to populate.
     */
    protected abstract void processChangeLine(String line,
            OutstandingIntegration oi);

    /**
     * Process a response line containing the file information.
     * 
     * @param line
     *            Response line with file information.
     * @param oi
     *            Outstanding integration to populate.
     */
    protected abstract void processFileLine(String line,
            OutstandingIntegration oi);

    /**
     * Get the configuration properties.
     * 
     * @return The configuration properties.
     */
    public DeterminationStrategyConfigurationCommand getConfiguration() {
        return configuration;
    }

    /**
     * Set the configuration properties.
     * 
     * @param cfg
     *            The configuration properties.
     */
    @Required
    public void setConfiguration(DeterminationStrategyConfigurationCommand cfg) {
        this.configuration = cfg;
    }

    /**
     * Get the command reader.
     * 
     * @return The command reader.
     */
    public CommandReader getCommandReader() {
        return commandReader;
    }

    /**
     * Set the command reader.
     * 
     * @param reader
     *            The command reader.
     */
    @Required
    public void setCommandReader(CommandReader reader) {
        this.commandReader = reader;
    }
}
